/* Bundle GPM program with GPM interpreter for use with Online Judges.
   Copyright (C) 2014-2015 P. Cherepanov and E. Batyrova.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 3
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, if not, see <http://www.gnu.org/licenses/>.
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <getopt.h>
#include "store.h"
#include "version.h"

extern char ggpm_source[];
extern int  ggpm_source_len;

unsigned long stack_size = 1024*1024;
char *input_fname = 0;
char *output_fname = 0;
FILE *input, *output;

static void error(int rc, const char *msg) {
    fprintf(stderr, "%s\n", msg);
        exit(rc);
}

static void usage(void) {
    printf(
        "Usage: ggpm2c [OPTIONS] [INPUT [OUTPUT]]\n"
        "Bundle ggpm interpreter and ggpm program into a self-contained C file.\n"
        "This file can be submitted to online testing systems that don't provide\n"
        "ggpm directly.\n\n"
        "Options:\n"
        "         --help                   display this help and exit\n"
        "         --version                output version information and exit\n"
        "     -i, --input-file             name of the file used for input, stdin by default\n"
        "     -o, --output-file            name of the file used for output, stdout by default\n"
        "     -s, --stack-size             maximum stack size, 1M elements by default\n\n"
        "Report bugs to: mathmoth@mathmoth.org\n"
        "Home page: http://code.google.com/g/ggpm\n"
    );
}

static void version(void) {
    printf(VERSION);
}

int main (int argc, char **argv) {
    int i, c, rc = 0;

    static struct option long_options[] = {
        {"help",    no_argument,       0, 0},
        {"version", no_argument,       0, 1},
        {"input-file",  required_argument, 0, 'i'},
        {"output-file", required_argument, 0, 'o'},
        {"stack-size",  required_argument, 0, 's'},
        {0, 0, 0, 0}
    };

    int option_index = 0;

    while((c = getopt_long (argc, argv, "i:o:s:", long_options, &option_index)) != -1) {

        switch (c) {
          case 0: /* help */
            usage();
            exit(0);

          case 1: /* version */
            version();
            exit(0);

          case 's':
            if(sscanf(optarg, "%lu", &stack_size) != 1 || stack_size < 1000) {
                fprintf(stderr, "Incorrect -s (--stack-size) value = %s\n", optarg);
                exit(1);
            }
            break;

          case 'i':
            input_fname = optarg;
            break;

          case 'o':
            output_fname = optarg;
            break;

          case '?':
            /* getopt_long() already printed an error message.  */
            break;

          default:
            usage();
            exit(1);
        }
    }

    if (optind + 2 < argc) {
        error(1, "Too many arguments");
    }
    input = stdin;
    output = stdout;
    if (optind < argc) {
        input = fopen(argv[optind],"rb");
        if (input == 0)
            error(1, "Cannot open input file");
        optind++;
    }
    if (optind < argc) {
        output = fopen(argv[optind],"wb");
        if (output == 0)
            error(1, "Cannot open output file");
        optind++;
    }

    if(fprintf(output,
        "/* Generated by ggpm2c. See http://code.google.com/p/ggpm */\n"
        "#include <stdio.h>\n"
        "#include <stdlib.h>\n"
        "#include <memory.h>\n"
        "#define getc_prog(f) (pcnt >= sizeof(prog) - 1 ? EOF : prog[pcnt++])\n"
        "FILE *input, *output;\n"
        "const int extended=1;\n") < 0)
        goto wr_error;

    if(fprintf(output,"const char prog[]=\n") < 0)
        goto wr_error;
    {
        StoreState ss = {0, 0, output};
        while ((c = getc(input)) != EOF) {
            if (store(c, &ss) < 0)
                goto wr_error;
        }
        if (finish(&ss) < 0)
            goto wr_error;
    }
    if (fprintf(output,";\n") < 0)
        goto wr_error;
    if (fprintf(output,"int pcnt=0;\n") < 0)
        goto wr_error;
    {
        int in_char=0, in_string=0;
        int col = 0;
        for (i = 0; i < ggpm_source_len; i++, col++) {
            c = ggpm_source[i];
            if (putc(c, output) == EOF)
                goto wr_error;
            switch(c) {
            case '\'':
                if (in_char) {
                    if ((i > 0 && ggpm_source[i - 1] != '\\') || (i > 1 && ggpm_source[i - 2] == '\\'))
                        in_char = 0;
                } else {
                    if (!in_string)
                        in_char = 1;
                }
                break;
            case '"':
                if (in_string) {
                    if ((i > 0 && ggpm_source[i - 1] != '\\') || (i > 1 && ggpm_source[i - 2] == '\\'))
                        in_string = 0;
                } else {
                    if (!in_char)
                        in_string = 1;
                }
                break;
            case ';':
                if (col > 60 && !in_char && !in_string) {
                    if (putc('\n', output) == EOF)
                        goto wr_error;
                    col = 0;
                }
            }
        }
    }
    if (fprintf(output, "\nint main(){\n") < 0)
        goto wr_error;
    if (input_fname) {
        if (fprintf(output, "if ((input = fopen(\"%s\",\"rb\")) == 0)\n", input_fname) < 0)
            goto wr_error;
        if (fprintf(output, "{fprintf(stderr, \"Error opening %s\"); exit(1);}\n", input_fname) < 0)
            goto wr_error;
    } else {
        if (fprintf(output, "input = stdin;\n") < 0)
            goto wr_error;
    }
    if (output_fname) {
        if (fprintf(output, "if ((output = fopen(\"%s\",\"wb\")) == 0)\n", output_fname) < 0)
            goto wr_error;
        if (fprintf(output, "{fprintf(stderr, \"Error opening %s\"); exit(1);}\n", output_fname) < 0)
            goto wr_error;
    } else {
        if (fprintf(output, "output = stdout;\n") < 0)
            goto wr_error;
    }
    if (fprintf(output, "GPM(%luuL);\n", stack_size) < 0)
        goto wr_error;
    if (input_fname)
        if (fprintf(output, "fclose(input);\n") < 0)
            goto wr_error;
    if (output_fname)
        if (fprintf(output, "fclose(output);\n") < 0)
            goto wr_error;
    if (fprintf(output,"return 0;\n""}\n") < 0)
        goto wr_error;
finish:
    if (input != stdin)
        fclose(input);
    if (output != stdout)
        fclose(output);
    return rc;
wr_error:
    rc = 1;
    fprintf(stderr, "Error writing output file\n");
    goto finish;
}
